"""同步/刷新流程任务定义数据表 mx_process_task_definitions

用途:
  在已经部署流程后, 通过 Flowable REST 拉取最新的流程定义 BPMN XML,
  解析 startEvent / userTask / endEvent 及 sequenceFlow 条件表达式,
  写入表 `mx_process_task_definitions` 以供接口快速查询未来步骤。

使用示例:
  python sync_process_task_definitions.py --keys leaveApproval purchaseApproval
  python sync_process_task_definitions.py --keys leaveApproval --clean
  python sync_process_task_definitions.py --local  (仅解析本地 flowable/processes 目录 BPMN 文件)

参数说明:
  --keys    指定要同步的流程 key 列表(空则使用默认: leaveApproval, purchaseApproval)
  --clean   同步前删除指定流程定义的旧记录(按 processDefinitionId)
  --local   不访问 Flowable, 仅解析本地文件(无法得知 processDefinitionId, 使用文件名作为临时 ID)

注意:
  本脚本假设 Flowable REST 地址与认证信息写在 Config 中。
"""
from __future__ import annotations
import os
import sys
import argparse
import json
import xml.etree.ElementTree as ET
from typing import List, Dict

try:
    from server.app.app import create_app  # type: ignore
    from server.config.config import Config  # type: ignore
    from server.app.utils.flowable_client import FlowableClient  # type: ignore
    from server.app.models.process_task_definition import ProcessTaskDefinition  # type: ignore
    from server.app import db  # type: ignore
except ModuleNotFoundError:
    sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
    from app.app import create_app  # type: ignore
    from config.config import Config  # type: ignore
    from app.utils.flowable_client import FlowableClient  # type: ignore
    from app.models.process_task_definition import ProcessTaskDefinition  # type: ignore
    from app import db  # type: ignore


def parse_bpmn_xml(xml_text: str) -> List[Dict]:
    """解析 BPMN XML 文本, 返回节点定义列表.

    返回的每个 dict: {activity_id, activity_name, activity_type, conditions}
    条件表达式来自 sequenceFlow 的 conditionExpression, 归并到目标节点。
    """
    definitions: List[Dict] = []
    try:
        root = ET.fromstring(xml_text)
        ns = {'bpmn2': 'http://www.omg.org/spec/BPMN/20100524/MODEL'}
        # 收集条件: targetRef -> [expr]
        conditions_map: Dict[str, List[str]] = {}
        for sf in root.findall('.//bpmn2:sequenceFlow', ns):
            target = sf.get('targetRef')
            cond_el = None
            for child in list(sf):
                if 'conditionExpression' in child.tag:
                    cond_el = child
                    break
            expr = cond_el.text.strip() if cond_el is not None and (cond_el.text or '').strip() else None
            if target and expr:
                conditions_map.setdefault(target, []).append(expr)
        # startEvent
        for se in root.findall('.//bpmn2:startEvent', ns):
            aid = se.get('id')
            aname = se.get('name') or aid
            definitions.append({'activity_id': aid, 'activity_name': aname, 'activity_type': 'startEvent', 'conditions': []})
        # userTask
        for ut in root.findall('.//bpmn2:userTask', ns):
            aid = ut.get('id')
            aname = ut.get('name') or aid
            definitions.append({'activity_id': aid, 'activity_name': aname, 'activity_type': 'userTask', 'conditions': conditions_map.get(aid, [])})
        # endEvent
        for ee in root.findall('.//bpmn2:endEvent', ns):
            aid = ee.get('id')
            aname = ee.get('name') or aid
            definitions.append({'activity_id': aid, 'activity_name': aname, 'activity_type': 'endEvent', 'conditions': []})
    except Exception as ex:
        print(f"⚠️  XML 解析失败: {ex}")
    return definitions


def fetch_process_definition_ids(client: FlowableClient, keys: List[str]) -> Dict[str, str]:
    """根据 key 查询最新版本的 processDefinitionId."""
    import requests
    from requests.auth import HTTPBasicAuth
    mapping: Dict[str, str] = {}
    for key in keys:
        url = f"{Config.FLOWABLE_REST_URL}/service/repository/process-definitions"
        resp = requests.get(url, params={'key': key}, auth=HTTPBasicAuth(Config.FLOWABLE_REST_USER, Config.FLOWABLE_REST_PASSWORD))
        if resp.status_code == 200:
            data = resp.json().get('data') or []
            if data:
                # 选择最高 version
                latest = sorted(data, key=lambda d: d.get('version', 0))[-1]
                mapping[key] = latest.get('id')
                print(f"✅ 获取流程定义: key={key} id={mapping[key]}")
            else:
                print(f"⚠️  未找到流程定义: key={key}")
        else:
            print(f"⚠️  请求失败 ({resp.status_code}): key={key}")
    return mapping


def sync_definitions(keys: List[str], clean: bool = False, local_only: bool = False) -> None:
    """执行同步逻辑."""
    app = create_app()
    with app.app_context():
        if local_only:
            print("🔄 本地解析模式 -- 不访问 Flowable")
            base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'flowable', 'processes'))
            for fname in os.listdir(base_dir):
                if not fname.endswith('.bpmn20.xml'):
                    continue
                path = os.path.join(base_dir, fname)
                try:
                    xml_text = open(path, 'r', encoding='utf-8').read()
                except Exception as ex:
                    print(f"⚠️  读取失败 {fname}: {ex}")
                    continue
                process_definition_id = fname  # 临时使用文件名
                definitions = parse_bpmn_xml(xml_text)
                if clean:
                    ProcessTaskDefinition.query.filter_by(process_definition_id=process_definition_id).delete()
                seq = 0
                for d in definitions:
                    db.session.add(ProcessTaskDefinition(
                        process_definition_id=process_definition_id,
                        activity_id=d['activity_id'],
                        activity_name=d['activity_name'],
                        activity_type=d['activity_type'],
                        sequence_index=seq,
                        conditions_json=json.dumps(d['conditions'], ensure_ascii=False)
                    ))
                    seq += 1
                db.session.commit()
                print(f"✅ 已同步本地流程 {fname}: {len(definitions)} 条")
            return

        client = FlowableClient(
            base_url=Config.FLOWABLE_REST_URL,
            username=Config.FLOWABLE_REST_USER,
            password=Config.FLOWABLE_REST_PASSWORD
        )
        mapping = fetch_process_definition_ids(client, keys)
        for key, proc_def_id in mapping.items():
            print(f"\n🔍 处理流程: {key} ({proc_def_id})")
            try:
                xml_text = client.get_process_definition_xml(proc_def_id)
            except Exception as ex:
                print(f"⚠️  获取 XML 失败: {ex}")
                continue
            definitions = parse_bpmn_xml(xml_text)
            if not definitions:
                print("⚠️  未解析到节点, 跳过")
                continue
            if clean:
                ProcessTaskDefinition.query.filter_by(process_definition_id=proc_def_id).delete()
            seq = 0
            for d in definitions:
                db.session.add(ProcessTaskDefinition(
                    process_definition_id=proc_def_id,
                    activity_id=d['activity_id'],
                    activity_name=d['activity_name'],
                    activity_type=d['activity_type'],
                    sequence_index=seq,
                    conditions_json=json.dumps(d['conditions'], ensure_ascii=False)
                ))
                seq += 1
            db.session.commit()
            print(f"✅ 已同步流程 key={key} 节点数={len(definitions)}")


def main():
    parser = argparse.ArgumentParser(description='同步流程任务定义至数据库')
    parser.add_argument('--keys', nargs='*', default=None, help='流程定义 key 列表')
    parser.add_argument('--clean', action='store_true', help='同步前清理旧记录')
    parser.add_argument('--local', action='store_true', help='仅解析本地 BPMN 文件 (不访问 Flowable)')
    args = parser.parse_args()

    keys = args.keys if args.keys else ['leaveApproval', 'purchaseApproval']
    print(f"🚀 开始同步: keys={keys} clean={args.clean} local_only={args.local}")
    sync_definitions(keys=keys, clean=args.clean, local_only=args.local)
    print("\n🎉 同步完成")


if __name__ == '__main__':
    main()